#![allow(dead_code)]

use gl;
use std::mem::{size_of, size_of_val};
use std::os::raw::c_void;
use crate::shaders::{VERTEX_INPUT_POSITION, TEX_COORD_INPUT_POSITION};
use core::ptr;

use zmath::{Vec3, Vec2};


pub struct Mesh {
    id: u32,
    vbo: u32,
    ibo: u32,
    len: i32,
}

impl Mesh {
    pub fn bind(&self) {
        unsafe {
            gl::BindVertexArray(self.id);
        }
    }

    pub fn draw(&self) {
        unsafe {
            gl::DrawElements(gl::TRIANGLES, self.len, gl::UNSIGNED_INT, ptr::null());
        }
    }
}

impl Drop for Mesh {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteVertexArrays(1, &self.id);
            gl::DeleteBuffers(2, [self.vbo, self.ibo].as_ptr());
        }
    }
}

pub struct MeshBuilder<'a> {
    vertices: &'a [Vec3],
    uvs: Option<&'a [Vec2]>,
    indices: &'a [u32],
    stride: i32,
}

impl<'a> MeshBuilder<'a> {
    pub fn new(vertices: &'a [Vec3], indices: &'a [u32]) -> Self {
        Self {
            vertices,
            uvs: None,
            indices,
            stride: 3
        }
    }

    pub fn with_uvs(&mut self, uvs: &'a [Vec2]) -> &mut Self {
        self.uvs = Some(uvs);
        self.stride += 2;
        self
    }


    pub fn build(self) -> Mesh {

        let mut vertices = Vec::new();

        if let Some(uvs) = self.uvs {
            for (i, v) in self.vertices.iter().enumerate() {
                vertices.push(v.0);
                vertices.push(v.1);
                vertices.push(v.2);

                vertices.push(uvs[i].0);
                vertices.push(uvs[i].1);
            }
        } else {
            for v in self.vertices.iter() {
                vertices.push(v.0);
                vertices.push(v.1);
                vertices.push(v.2);
            }
        };
        vertices.shrink_to_fit();

        let (vao, vbo, ibo) = unsafe {
            let mut vao = 0u32;
            let mut buffers = [0u32; 2];
            gl::GenVertexArrays(1, &mut vao);
            gl::GenBuffers(2, buffers.as_mut_ptr());

            gl::BindVertexArray(vao);

            gl::BindBuffer(gl::ARRAY_BUFFER, buffers[0]);
            gl::BufferData(gl::ARRAY_BUFFER,
                           (vertices.len() * size_of::<f32>()) as isize,
                           vertices.as_ptr() as *const c_void,
                           gl::STATIC_DRAW);

            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, buffers[1]);
            gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, 
                           size_of_val(self.indices) as isize,
                           self.indices.as_ptr() as *const c_void,
                           gl::STATIC_DRAW);


            gl::VertexAttribPointer(VERTEX_INPUT_POSITION, 
                            3, 
                            gl::FLOAT, 
                            gl::FALSE, 
                            self.stride * size_of::<f32>() as i32, 
                            0 as *const c_void);

            gl::EnableVertexAttribArray(VERTEX_INPUT_POSITION);

            if self.stride > 3 {
                gl::VertexAttribPointer(TEX_COORD_INPUT_POSITION, 2, gl::FLOAT, gl::FALSE, self.stride * size_of::<f32>() as i32, (3 * size_of::<f32>()) as *const c_void);
                gl::EnableVertexAttribArray(TEX_COORD_INPUT_POSITION);
            }

            gl::BindBuffer(gl::ARRAY_BUFFER, 0);
            gl::BindVertexArray(0);
            

            (vao, buffers[0], buffers[1])
        };

        Mesh {
            id: vao,
            vbo,
            ibo,
            len: self.vertices.len() as i32,
        }
    }
}